package com.java.code.zookeeper.zkclient;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author weichuanyuan
 * @e-mail wei.cy@bsoft.com.cn
 * @date 2021/10/18 16:16
 * @package com.java.code.zookeeper.zkclient
 * @description
 */
@Component
@Slf4j
public class ZkUtils {
    @Autowired(required = false)
    @Qualifier("zkClient")
    private ZooKeeper zkClient;

    /**
     * 判断指定节点是否存在
     * <p>
     * stat的解释
     * cZxid：这是导致创建znode更改的事务ID。
     * mZxid：这是最后修改znode更改的事务ID。
     * pZxid：这是用于添加或删除子节点的znode更改的事务ID。
     * ctime：表示从1970-01-01T00:00:00Z开始以毫秒为单位的znode创建时间。
     * mtime：表示从1970-01-01T00:00:00Z开始以毫秒为单位的znode最近修改时间。
     * dataVersion：表示对该znode的数据所做的更改次数。
     * cversion：这表示对此znode的子节点进行的更改次数。
     * aclVersion：表示对此znode的ACL进行更改的次数。
     * ephemeralOwner：如果znode是ephemeral类型节点，则这是znode所有者的 session ID。 如果znode不是ephemeral节点，则该字段设置为零。
     * dataLength：这是znode数据字段的长度。
     * numChildren：这表示znode的子节点的数量。
     *
     * @param path
     * @param needWatch 指定是否复用zookeeper中默认的Watcher
     * @return
     */
    public Stat exists(String path, boolean needWatch) {
        try {
            return zkClient.exists(path, needWatch);
        } catch (Exception e) {
            log.error("【断指定节点是否存在异常】{},{}", path, e);
            return null;
        }
    }

    /**
     * 检测结点是否存在 并设置监听事件
     * 三种监听类型： 创建，删除，更新
     *
     * @param path
     * @param watcher 传入指定的监听类
     * @return
     */
    public Stat exists(String path, Watcher watcher) {
        try {
            return zkClient.exists(path, watcher);
        } catch (Exception e) {
            log.error("【断指定节点是否存在异常】{},{}", path, e);
            return null;
        }
    }

    /**
     * 创建持久化节点
     *
     * @param path
     * @param data
     */
    public boolean createNode(String path, String data) {
        try {
            zkClient.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            return true;
        } catch (Exception e) {
            log.error("【创建持久化节点异常】{},{},{}", path, data, e);
            return false;
        }
    }


    /**
     * 修改持久化节点
     *
     * @param path
     * @param data
     */
    public boolean updateNode(String path, String data) {
        try {
            //zk的数据版本是从0开始计数的。如果客户端传入的是-1，则表示zk服务器需要基于最新的数据进行更新。如果对zk的数据节点的更新操作没有原子性要求则可以使用-1.
            //version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
            zkClient.setData(path, data.getBytes(), -1);
            return true;
        } catch (Exception e) {
            log.error("【修改持久化节点异常】{},{},{}", path, data, e);
            return false;
        }
    }

    /**
     * 删除持久化节点
     *
     * @param path
     */
    public boolean deleteNode(String path) {
        try {
            //version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
            zkClient.delete(path, -1);
            return true;
        } catch (Exception e) {
            log.error("【删除持久化节点异常】{},{}", path, e);
            return false;
        }
    }

    /**
     * 获取当前节点的子节点(不包含孙子节点)
     *
     * @param path 父节点path
     */
    public List<String> getChildren(String path) throws KeeperException, InterruptedException {
        List<String> list = zkClient.getChildren(path, false);
        return list;
    }

    /**
     * 获取指定节点的值
     *
     * @param path
     * @return
     */
    public String getData(String path, Watcher watcher) {
        try {
            Stat stat = new Stat();
            byte[] bytes = zkClient.getData(path, watcher, stat);
            return new String(bytes);

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 添加监听
     *
     * @param path    节点路径
     * @param watcher 监听器
     * @param mode    监听模式，PERSISTENT是不监听子孙节点，PERSISTENT_RECURSIVE是递归监听子孙节点
     */
    public void addWatch(String path, Watcher watcher, AddWatchMode mode) {
        try {
            zkClient.addWatch(path, watcher, mode);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 移除监听
     *
     * @param path        节点路径
     * @param watcher     原来添加的那个Watcher监听对象，不能是null
     * @param watcherType 指定要移除监听的哪部分，Any是移除整个监听，Data是移除对数据的监听，Children是移除对子节点的递归监听
     * @param local       指定未连接到zkServe时，是否移除本地监听部分
     */
    public void removeWatch(String path, Watcher watcher, Watcher.WatcherType watcherType, boolean local) {
        try {
            zkClient.removeWatches(path, watcher, watcherType, local);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    /**
     * ZK的节点有5种操作权限：
     * CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限，这5种权限简写为crwda(即：每个单词的首字符缩写)
     * 注：这5种权限中，delete是指对子节点的删除权限，其它4种权限指对自身节点的操作权限
     * <p>
     * 身份的认证有4种方式：
     * world：默认方式，相当于全世界都能访问
     * auth：代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
     * digest：即用户名:密码这种方式认证，这也是业务系统中最常用的
     * ip：使用Ip地址认证
     * <p>
     * <p>
     * 设置ACL权限，设置权限之后，连接zkServer进行操作时，都需要先验证用户
     * 也可以再创建节点的时候增加权限
     *
     * @param path
     * @param aclList
     * @param version 版本号
     */
    public void setACL(String path, List<ACL> aclList, int version) {
        try {
            zkClient.setACL(path, aclList, version);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 验证权限
     *
     * @param acl
     */
    public void auth(String scheme, String acl) {
        zkClient.addAuthInfo(scheme, acl.getBytes());
    }

    /**
     * 获取权限列表,zkClient.getAC还有一种异步回调的方式
     *
     * @param path
     */
    public List<ACL> getACL(String path) {
        List<ACL> acl = null;
        try {
            acl = zkClient.getACL(path, null);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return acl;
    }


}
